package com.ym521.facecheck.core

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
import com.ym521.facecheck.FaceCheck
import com.ym521.facecheck.bean.AngleWeight
import com.ym521.facecheck.callback.FaceCheckCallback
import com.ym521.facecheck.utils.LogFC
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.opencv.android.Utils
import org.opencv.core.Core
import org.opencv.core.Mat
import org.opencv.core.Size
import org.opencv.imgproc.Imgproc
import org.opencv.objdetect.FaceDetectorYN

/**
 *@author Ym
 *E-mail: 2435970206@qq.com
 *createTime:2024/6/22
 *explain: 图像角度 优化处理引擎
 *  其可以处理图片的 90、180、270 等角度的兼容检测处理
 */
internal class ImageAngleEngine private constructor() : IImageAngleEngine {

    private lateinit var matList: Array<Mat>
    private lateinit var facesMat: Mat

    private val angleList by lazy {
        arrayOf(AngleWeight(0, 0), AngleWeight(1, 0), AngleWeight(2, 0), AngleWeight(3, 0))
    }

    private val memoryAngleArray = arrayOf(0f, 90f, 180f, 270f)

    //开启记忆下的成功检测到人脸的角度
    private var memoryAngle = DEFAULT_ANGLE

    companion object {
        private const val DEFAULT_ANGLE = -100f
        private const val TAG = "AngleHandlerEngine"
        fun build(): IImageAngleEngine {
            return ImageAngleEngine()
        }
    }

    private lateinit var detector: FaceDetectorYN
    private var faceVisualizeEngine: IImageFaceVisualizeEngine? = null

    override fun initMat(detector: FaceDetectorYN): IImageAngleEngine {
        LogFC.d(TAG,"initMat()")
        this.detector = detector
        matList = Array(3) { Mat() }
        facesMat = Mat()
        faceVisualizeEngine = ImageFaceVisualizeEngine
            .build()
            .initEngine()
        return this
    }

    private var faceCheckCallBack: FaceCheckCallback? = null
    override fun setFaceCheckCallBack(callback: FaceCheckCallback) {
        LogFC.d(TAG,"setFaceCheckCallBack()")
        this.faceCheckCallBack = callback
    }

    private var isSetting = false
    override fun setFaceDetectorSize(size: Size) {
        LogFC.d(TAG,"setFaceDetectorSize(${size.width} x ${size.height})")
        detector.inputSize = size
        isSetting = true
    }

    override fun isFaceDetectorSizeSetting(): Boolean {
        LogFC.d(TAG,"isFaceDetectorSizeSetting($isSetting)")
        return isSetting
    }


    /**
     * 第一个方案
     * 通过旋转 Mat实现
     */
    override suspend fun imageAngleHandler(bgaMat: Mat) {
        if (!this::matList.isInitialized || !this::facesMat.isInitialized) return
        LogFC.d(TAG, "imageAngleHandler()")
        if (_FaceCheck.memoryEnabled && memoryAngle != DEFAULT_ANGLE) {
            when (memoryAngle) {
                memoryAngleArray[0] -> checkAngle0(detector, bgaMat, facesMat)
                memoryAngleArray[1] -> checkAngle90(detector, bgaMat, facesMat)
                memoryAngleArray[2] -> checkAngle180(detector, bgaMat, facesMat)
                else -> checkAngle270(detector, bgaMat, facesMat)
            }
        } else {
            val angleList = angleList.sortedWith(compareByDescending { it.weight })
            for (it in angleList) {
                when (it.index) {
                    0 -> if (checkAngle0(detector, bgaMat, facesMat)) {
                        it.weight++
                        memoryAngle = memoryAngleArray[0]
                        break
                    } else {
                        if (it.weight > 3) {
                            it.weight--
                            break
                        }
                    }
                    1 -> if (checkAngle90(detector, bgaMat, facesMat)) {
                        it.weight++
                        memoryAngle = memoryAngleArray[1]
                        break
                    } else {
                        if (it.weight > 3) {
                            it.weight--
                            break
                        }
                    }
                    2 -> if (checkAngle180(detector, bgaMat, facesMat)) {
                        it.weight++
                        memoryAngle = memoryAngleArray[2]
                        break
                    } else {
                        if (it.weight > 3) {
                            it.weight--
                            break
                        }
                    }
                    else -> if (checkAngle270(detector, bgaMat, facesMat)) {
                        it.weight++
                        memoryAngle = memoryAngleArray[3]
                        break
                    } else {
                        if (it.weight > 3) {
                            it.weight--
                            break
                        }
                    }
                }
            }
        }
    }

    private suspend fun checkAngle0(
        detectorYN: FaceDetectorYN,
        bgaMat: Mat,
        facesMat: Mat
    ): Boolean {
        detectorYN.detect(bgaMat, facesMat)
        if (facesMat.total() > 0) {
            withContext(Dispatchers.Main) {
                faceCheckCallBack?.onCheckResult(
                    facesMat.total() > 0,
                    0,
                    if (_FaceCheck.visualizeEnabled)
                        faceVisualizeEngine?.visualizeFace(bgaMat, facesMat)
                    else
                        null
                )
            }
        }
        return facesMat.total() > 0
    }

    private suspend fun checkAngle90(
        detectorYN: FaceDetectorYN,
        bgaMat: Mat,
        facesMat: Mat
    ): Boolean {
        Core.transpose(bgaMat, matList[0])
        Core.flip(matList[0], matList[0], 0)
        detectorYN.detect(matList[0], facesMat)
        if (facesMat.total() > 0) {
            withContext(Dispatchers.Main) {
                faceCheckCallBack?.onCheckResult(
                    facesMat.total() > 0,
                    90,
                    if (_FaceCheck.visualizeEnabled)
                        faceVisualizeEngine?.visualizeFace(matList[0], facesMat)
                    else
                        null
                )
            }
        }
        return facesMat.total() > 0
    }

    private suspend fun checkAngle180(
        detectorYN: FaceDetectorYN,
        bgaMat: Mat,
        facesMat: Mat
    ): Boolean {
        Core.flip(bgaMat, matList[1], -1)
        detectorYN.detect(matList[1], facesMat)
        if (facesMat.total() > 0) {
            withContext(Dispatchers.Main) {
                faceCheckCallBack?.onCheckResult(
                    facesMat.total() > 0,
                    180,
                    if (_FaceCheck.visualizeEnabled)
                        faceVisualizeEngine?.visualizeFace(matList[1], facesMat)
                    else
                        null
                )
            }
        }
        return facesMat.total() > 0
    }

    private suspend fun checkAngle270(
        detectorYN: FaceDetectorYN,
        bgaMat: Mat,
        facesMat: Mat
    ): Boolean {
        Core.transpose(bgaMat, matList[2])
        Core.flip(matList[2], matList[2], 1)
        detectorYN.detect(matList[2], facesMat)
        if (facesMat.total() > 0) {
            withContext(Dispatchers.Main) {
                faceCheckCallBack?.onCheckResult(
                    facesMat.total() > 0,
                    270,
                    if (_FaceCheck.visualizeEnabled)
                        faceVisualizeEngine?.visualizeFace(matList[2], facesMat)
                    else
                        null
                )
            }
        }
        return facesMat.total() > 0
    }


    /**
     * 第二个方案
     * 通过旋转 bitmap 实现
     */
    override suspend fun imageAngleHandler2(image: Bitmap, rgbaMat: Mat, bgaMat: Mat) {
        Utils.bitmapToMat(rotateSquareBitmap(image, memoryAngle), rgbaMat)
        Imgproc.cvtColor(rgbaMat, bgaMat, Imgproc.COLOR_RGBA2BGR)
        if (memoryAngle != DEFAULT_ANGLE) {
            val has = imageAngleCheck(image, rgbaMat, bgaMat,memoryAngle)
            LogFC.d(TAG,"imageAngleHandler2 memory has:$has")
            withContext(Dispatchers.Main) {
                val facesImage = if (_FaceCheck.visualizeEnabled) {
                    facesMat.let { faceVisualizeEngine?.visualizeFace(rgbaMat, it) }
                } else {
                    null
                }
                faceCheckCallBack?.onCheckResult(has, memoryAngle.toInt(), facesImage)
            }
            return
        }

        for (angle in memoryAngleArray) {
            val has = imageAngleCheck(image, rgbaMat, bgaMat,angle)
            LogFC.d(TAG,"imageAngleHandler2  has:$has")
            if (has) {
                this.memoryAngle = angle
            }
            withContext(Dispatchers.Main) {
                val facesImage = if (_FaceCheck.visualizeEnabled) {
                    facesMat.let { faceVisualizeEngine?.visualizeFace(rgbaMat, it) }
                } else {
                    null
                }
                faceCheckCallBack?.onCheckResult(has, memoryAngle.toInt(), facesImage)
            }
            break
        }
    }

    /**
     * 人脸检测
     */
    private fun imageAngleCheck(image: Bitmap, rgbaMat: Mat, bgaMat: Mat,angle:Float): Boolean {
        Utils.bitmapToMat(rotateSquareBitmap(image, angle), rgbaMat)
        Imgproc.cvtColor(rgbaMat, bgaMat, Imgproc.COLOR_RGBA2BGR)
        detector.detect(bgaMat, facesMat)
        return facesMat.total() > 0

    }

    /**
     * 旋转 bitmap
     */
    private val matrix = Matrix()
    private var length = 0
    private var squareBitmap: Bitmap? = null

    private fun rotateSquareBitmap(srcBitmap: Bitmap, degrees: Float): Bitmap? {
        if (length == 0)
            length = srcBitmap.width.coerceAtLeast(srcBitmap.height)

        if (squareBitmap == null)
            squareBitmap = Bitmap.createBitmap(length, length, srcBitmap.config)
        squareBitmap?.let {
            val canvas = Canvas(it)
            matrix.reset()
            matrix.postRotate(
                degrees,
                (srcBitmap.width / 2).toFloat(),
                (srcBitmap.height / 2).toFloat()
            )
            canvas.drawColor(Color.BLACK)
            canvas.drawBitmap(srcBitmap, matrix, null)
        }
        return squareBitmap
    }


    override fun release() {
        if (!facesMat.empty())
            facesMat.release()
        if (this::matList.isInitialized) {
            matList.forEach {
                it.release()
            }
        }
        squareBitmap?.recycle()
        squareBitmap = null
        isSetting = false
    }
}